#include "ByteCodeLoader.h"

#include "Reflection/FunctionList.h"

#include "ScriptRuntime/Type/ScriptFunction.h"
#include "ScriptRuntime/Type/GSharpClass.h"
#include "ScriptRuntime/Type/GSharpStruct.h"
#include "ScriptRuntime/Type/ScriptContent.h"
#include "ScriptRuntime/Type/GSharpPropertyList.h"

#include "ScriptRuntime/ByteCode/ByteCodeReader.h"
#include "ScriptRuntime/ByteCode/ByteCodeFile.h"

#include "Object/Object.h"

#include "ScriptRuntime/ScriptInnerType.h"

#include "ScriptRuntime/Type/GSharpFieldProperty.h"
#include "ScriptRuntime/ByteCode/ByteCodeSectionTypes.h"
#include "ScriptRuntime/Type/GSharpGenericClass.h"
#include "ScriptRuntime/Type/GSharpGeneralizedClass.h"
#include "ScriptRuntime/Type/GSharpGenericClass.h"
#include "ScriptRuntime/Type/GSharpGenericStruct.h"
#include "ScriptRuntime/Type/GSharpEnum.h"

#include "Reflection/VectorType.h"
#include "Reflection/PairType.h"
#include "Reflection/MapType.h"
#include "Reflection/PlaceholderType.h"
#include "Reflection/TypeType.h"
#include "Reflection/SubclassOfType.h"
#include "Reflection/ObjectPtr.h"

#include "ScriptRuntime/Interpreter/RuntimeScriptFile.h"

#include "ScriptRuntime/ScriptSubsystem.h"

#include "ScriptRuntime/Debug/Breakpoint.h"

CByteCodeLoader::CByteCodeLoader()
{
    

}

void CByteCodeLoader::LoadByteCodeFile(std::shared_ptr<CByteCodeFile> InByteCodeFile,const CString& InScriptLibraryName)
{
    ScriptLibraryName=InScriptLibraryName;

    std::shared_ptr<CByteCodeReader> ByteCodeReader = std::make_shared<CByteCodeReader>(InByteCodeFile);

    LoadFile(ByteCodeReader);


    //copy base type property to derived class
    for(CType* ScriptType : CType::GetTypeTable().GetAllScriptTypes() )
    {
        //just copy base type property to derived class
        //do not consider base type's base type
        //because  base type always before derived class in Type list,
        //before this, we have already copyed base type's base type property to base type
        if(ScriptType->BaseType && ScriptType->BaseType->Properties)
        {
            if(ScriptType->IsScriptClassType())
            {
                CPropertyList::AppendProperties(
                ScriptType->BaseType->Properties
                ,ScriptType->Properties
                ,true
                );
            }
            else if(ScriptType->IsScriptStructType())
            {
                CPropertyList::AppendProperties(
                ScriptType->BaseType->Properties
                ,RObject::StaticClass()->Properties
                ,true
                );
            }
        }
    }


    //post construct all classes
    for(CType* ScriptType : CType::GetTypeTable().GetAllScriptTypes() )
    {
        if(ScriptType->PostConstructFunction)
        {
            ScriptType->PostConstructFunction();
        }

    }
}

void CByteCodeLoader::LoadFile(std::shared_ptr<CByteCodeReader> InByteCodeFileReader)
{


    //create source files
    AllScriptFiles.Reserve(InByteCodeFileReader->GetSourceFileCount());
    for(uint32_t i=0;i<InByteCodeFileReader->GetSourceFileCount();i++)
    {
        std::shared_ptr<CRuntimeScriptFile> NewScriptFile = std::make_shared<CRuntimeScriptFile>();
        NewScriptFile->FilePath=  InByteCodeFileReader->GetSourceFile(i).FilePath;
        auto LineCount=  InByteCodeFileReader->GetSourceFile(i).LineCount;
        NewScriptFile->BreakpointSlots.Resize(LineCount);
        AllScriptFiles.Add(NewScriptFile);
    }
    RScriptSubsystem::Get()->SetScriptFiles(AllScriptFiles);

    while(!InByteCodeFileReader->Finished())
    {
        EByteCodeSectionType Instruction;
        *InByteCodeFileReader >> Instruction;

        switch (Instruction)
        {
        case EByteCodeSectionType::Namespace:
        {
            uint32_t NamespaceContentLength;
            *InByteCodeFileReader >> NamespaceContentLength;

            uint32_t EndIndex= InByteCodeFileReader->GetOffset() + NamespaceContentLength-1;

            CString NamespaceName;
            *InByteCodeFileReader >> NamespaceName;

            TVector<CString> ParentNameScopeID; 
            *InByteCodeFileReader >> ParentNameScopeID;

            // //1. find parent name scope
            // CNameScope* ParentScope=CNamespace::FindNameScope(ParentNameScopeID);

            // SNameScopeID NewNameScopeID= ParentNameScopeID;
            // NewNameScopeID.Add(NamespaceName);
            // CNameScope* NewNameScope= new CNamespace(NewNameScopeID);
            // ParentScope->AddSubNameScope(NewNameScope);
            
            break;

        }


        case EByteCodeSectionType::Class:
        {
            uint32_t ClassContentLength;
            *InByteCodeFileReader >> ClassContentLength;

            uint32_t EndIndex= InByteCodeFileReader->GetOffset() + ClassContentLength-1;

            CByteCodeFile::SClassInfo ClassInfo;
            *InByteCodeFileReader >> ClassInfo;
            
            assert(ClassInfo.TypeID.IsScriptType());
            // {
            //     //skip any native class 
            //     InByteCodeFileReader->SetOffset(EndIndex+1);
            //     break;
            // }

            //find parent name scope
            //CNameScope* ParentScope=CNamespace::FindNameScope(ClassInfo.NameScopeID);

            CType * NewClass=nullptr;

            CType * GenericNewClass=nullptr;
            if(ClassInfo.GenericPlaceholders.Empty() )
            {
                if(ClassInfo.bIsStruct )
                {
                    NewClass = new CGSharpStruct(ClassInfo.TypeID,ETypeFlags::None);
                }
                else if(ClassInfo.bIsDelegate)
                {
                    auto FunctionType= CType::FindType(ClassInfo.DelegateFunctionType);
                    assert(FunctionType->IsFunctionType() && "Argument type is not function type");
                    
                    NewClass=new CScriptDelegateGenerialized(
                        reinterpret_cast<CFunctionTypeGeneralized*>(FunctionType)
                        ,ETypeFlags::Script
                        );
                    assert(NewClass->ID==ClassInfo.TypeID && "Delegate type id not match");
                }
                else if(ClassInfo.bIsEnum)
                {
                    NewClass = new CGSharpEnum(
                        ClassInfo.TypeID
                        ,TMap<int64_t,CString>::Zip(ClassInfo.EnumValues,ClassInfo.EnumNames)
                        );
                }
                else
                {
                    NewClass = new CGSharpClass(
                        ClassInfo.RawClassTypeID
                        ,ETypeFlags::None
                        );
                }
            }

            else
            {


                assert(!ClassInfo.bIsEnum && "Generic enum is not supported");

                TVector<STypeID> PlaceholderTypes;
                for(auto& GenericPlaceholder: ClassInfo.GenericPlaceholders)
                {
                    PlaceholderTypes.Add(GenericPlaceholder.TypeID);
                }

                if(ClassInfo.bIsStruct)
                {
                    GenericNewClass = new CGSharpGenericStruct(
                        ClassInfo.TypeID
                        ,PlaceholderTypes
                        );
                }
                else
                {
                    GenericNewClass = new CGSharpGenericClass(
                        ClassInfo.RawClassTypeID
                        );
                }
                NewClass = GenericNewClass;
            }

            STypeID BaseTypeID= ClassInfo.BaseTypeID;

            if(BaseTypeID.IsValid())
            {

                    auto BaseType= CType::FindType(BaseTypeID);
                    assert(BaseType && "Base type not found");

                    NewClass->SetBaseType(BaseType);
            }

            break;

        }
        case EByteCodeSectionType::FunctionType:
        {
            CByteCodeFile::SFunctionType FunctionType;
            *InByteCodeFileReader >> FunctionType;

                //set return type and parameter types
                SDecoratedType ReturnType=SDecoratedType(CType::FindType(FunctionType.ReturnType.TypeID));

                TVector<SDecoratedType> ParameterTypes;
                ParameterTypes.Reserve(FunctionType.ParameterTypes.Num());
                for(auto& ParameterType : FunctionType.ParameterTypes)
                {
                    auto Type= CType::FindType(ParameterType.TypeID);
                    assert(Type && "Parameter type not found");
                    ParameterTypes.Add(SDecoratedType(Type));
                }


            CFunctionTypeGeneralized* FunctionTypeGeneralized=
                new CFunctionTypeGeneralized(ReturnType,ParameterTypes);
            assert(FunctionTypeGeneralized->ID==FunctionType.TypeID && "Function type id not match");
            
            break;
        }

        case EByteCodeSectionType::GeneralizedClass:
        {
            CByteCodeFile::SGeneralizedClasseInfo GeneralizedClassInfo;
            *InByteCodeFileReader >>GeneralizedClassInfo;


            TVector<CType*> ArgumentTypes;

            for(auto& ArgumentInfo : GeneralizedClassInfo.ArgumentInfos)
            {
                auto ArgumentType= CType::FindType(ArgumentInfo.TypeID);
                //assert(ArgumentType && "Argument type not found");
                ArgumentTypes.Add(ArgumentType);
            }

            CType* TemplateType = CType::FindType(GeneralizedClassInfo.TemplateID);

            //try generialized by native template type
            if(!TemplateType->IsScriptType())
            {
                if(TemplateType== CTVectorType::GetStaticType())
                {
                    assert(ArgumentTypes.Num()==1 && "Vector must have one argument");
                    auto NewType=new CTVectorGeneralized(ArgumentTypes[0]);
                    assert(NewType->ID==GeneralizedClassInfo.TypeID && "Vector type id not match");
                    break ;
                }
                else if(TemplateType ==CTOptionalType::GetStaticType())
                {
                    assert(ArgumentTypes.Num()==1 && "Optional must have one argument");
                    auto NewType=new CTOptionalTypeGenerialized(ArgumentTypes[0],ETypeFlags::Script);
                    assert(NewType->ID==GeneralizedClassInfo.TypeID && "Optional type id not match");
                    break ;
                }
                else if(TemplateType== CPairType::GetStaticType())
                {
                    assert(ArgumentTypes.Num()==2 && "Pair must have two argument");
                    auto NewType=new CPairGeneralized(ArgumentTypes[0],ArgumentTypes[1],0);
                    assert(NewType->ID==GeneralizedClassInfo.TypeID && "Pair type id not match");
                    break ;
                }
                else if(TemplateType == CMapType::GetStaticType())
                {
                    assert(ArgumentTypes.Num()==2 && "Map must have two argument");
                    auto NewType=new CMapGeneralized(ArgumentTypes[0],ArgumentTypes[1],0);
                    assert(GeneralizedClassInfo.TypeID==NewType->ID);
                    break ;
                }
                else if(TemplateType == CTypeOfType::GetStaticType())
                {
                    assert(ArgumentTypes.Num()==1 && "TypeOf must have only one argument");
                    auto NewType=new CTypeOfGeneralized(ArgumentTypes[0]);
                    assert(NewType->ID==GeneralizedClassInfo.TypeID && "TypeOf type id not match");
                    break ;
                }
                else if(TemplateType == CSubclassOfType::GetStaticType())
                {
                    assert(ArgumentTypes.Num()==1 && "SubclassOf must have only one argument");
                    auto NewType=new CSubclassOfGeneralized(ArgumentTypes[0]);
                    assert(NewType->ID==GeneralizedClassInfo.TypeID && "SubclassOf type id not match");
                    break ;                
                }
                else if(TemplateType == CSharedObjectPtrType::GetStaticType())
                {
                    assert(ArgumentTypes.Num()==1 && "SharedObjectPtr must have only one argument");
                    auto ClassType= ArgumentTypes[0];
                    assert(ClassType->IsClassType() && "SharedObjectPtr must have class type argument");
                    auto Class= reinterpret_cast<CClass*>(ClassType);
                    auto NewType=new CObjectPtrTypeGeneralized(Class);
                    assert(NewType->ID==GeneralizedClassInfo.TypeID && "SharedObjectPtr type id not match");
                    break ;
                }   
                else if(TemplateType== CFunctionType::GetStaticType())
                {
                    assert(ArgumentTypes.Num()>=1 && "FunctionType  at least have return type argument");
                    auto ReturnType= ArgumentTypes[0];
                    TVector<SDecoratedType> ParameterTypes;
                    for(uint64_t i=1;i<ArgumentTypes.Num();++i)
                    {
                        ParameterTypes.Add(ArgumentTypes[i]);
                    }
                    auto FunctionType=new CFunctionTypeGeneralized(
                        ReturnType
                        ,ParameterTypes
                        );
                    assert(FunctionType->ID==GeneralizedClassInfo.TypeID && "Function type id not match");
                    break ;
                }
                else if(TemplateType== CScriptDelegateType::GetStaticType())
                {
                    assert(ArgumentTypes.Num() >=1 && "ScriptDelegate at least have a return type argument");
                    auto ReturnType= ArgumentTypes[0];
                    TVector<SDecoratedType> ParameterTypes;
                    for(uint64_t i=1;i<ArgumentTypes.Num();++i)
                    {
                        ParameterTypes.Add(ArgumentTypes[i]);
                    }
                    
                    //find function type,function type must be created before delegate
                    auto FunctionType= CFunctionType::FindFunctionType(ReturnType,ParameterTypes);
                    assert(FunctionType && "Function type not found");

                    auto NewType=new CScriptDelegateGenerialized(
                        FunctionType
                        ,ETypeFlags::Script
                        );
                    assert(NewType->ID==GeneralizedClassInfo.TypeID && "ScriptDelegate type id not match");

                    break ;
                }

                assert(false && "Unknown native template type");
            }
            assert(TemplateType->IsScriptType() && "native template type must addressed above");

            // else if(TemplateType == CScriptDelegateType::GetStaticType())
            // {
            //     assert(ArgumentTypes.Num()==1 && "ScriptDelegate must have only one argument");
            //     auto FunctionType= ArgumentTypes[0];
            //     assert(FunctionType->IsFunctionType() && "ScriptDelegate must have function type argument");
            //     auto Function= reinterpret_cast<CFunctionTypeGeneralized*>(FunctionType);
            //     new CScriptDelegateGenerialized(GeneralizedClassInfo.TypeID,Function);
            //     break;
            // }

        
            CString GenericClassName= TemplateType->GetFullName()+ U"<";
            for(uint64_t GenericTypeIndex=0;
                    GenericTypeIndex<GeneralizedClassInfo.ArgumentInfos.Num();
                    ++GenericTypeIndex)
            {
                    if(GenericTypeIndex== GeneralizedClassInfo.ArgumentInfos.Num()-1)
                    {
                        GenericClassName+= ArgumentTypes[GenericTypeIndex]->GetFullName();
                        break;
                    }
                    else
                    {
                        GenericClassName+= ArgumentTypes[GenericTypeIndex]->GetFullName()+ U",";
                    }

            }
            GenericClassName+= U">";

            if(TemplateType->IsStructType())
            {
                //generialize struct
                //because template type is script type, 
                //so generialized type's function and property 
                //created by compiler and will be loaded from byte code later
                //so we do not need to create generialized type here
                CType* GeneralizedType=new CGSharpStruct(
                 GeneralizedClassInfo.TypeID
                , ETypeFlags::Generalized
                );
                
                GeneralizedType->GeneralizedInfo= new CGeneralizedInfo();
                GeneralizedType->GeneralizedInfo->TemplateType=TemplateType;
                for(auto& InArguments: GeneralizedClassInfo.ArgumentInfos)
                {
                    auto ArgumentType= CType::FindType(InArguments.TypeID);
                    assert(ArgumentType && "Argument type not found");
                    GeneralizedType->GeneralizedInfo->TypeArguments.Add(ArgumentType);
                }
                
                TemplateType->GenericInfo->AddGeneralizedType(
                    GeneralizedType->GeneralizedInfo->TypeArguments
                    ,GeneralizedType);

            }
            else
            {
                auto GenericClass = new CGSharpGeneralizedClass(
                    GeneralizedClassInfo.RawClassTypeID
                    ,reinterpret_cast<CGSharpGenericClass*>(TemplateType)
                    ,ArgumentTypes
                    );
                assert(GenericClass->ID==GeneralizedClassInfo.RawClassTypeID && "Generic class type id not match");
                GenericClass->BaseType= CType::FindType(GeneralizedClassInfo.BaseTypeID.GetValue());
                assert(GenericClass->BaseType && "Base type not found");
            }            

            break;
        }

        case EByteCodeSectionType::ClassContent:
        {
            uint32_t ClassContentLength;
            *InByteCodeFileReader >> ClassContentLength;

            uint32_t EndIndex= InByteCodeFileReader->GetOffset() + ClassContentLength-1;

            STypeID ClassTypeID;
            *InByteCodeFileReader >> ClassTypeID;

            auto ClassType= CType::FindType(ClassTypeID);
            assert(ClassType && "Class type not found");

            if(ClassType->IsSharedObjectPtrType())
            {
                CObjectPtrTypeGeneralized* SharedObjectPtrType= reinterpret_cast<CObjectPtrTypeGeneralized*>(ClassType);
                ClassType= SharedObjectPtrType->GetObjectClass();
            }

            LoadClassContent(InByteCodeFileReader,ClassType,EndIndex);

            break;
        }
        default:
            assert(false && "Unknown instruction");
        }

    }

}

void CByteCodeLoader::LoadClassContent(std::shared_ptr<CByteCodeReader> InByteCodeFileReader, CType * ParentType, uint32_t InClassEndByte)
{
    //CType * ParentType = InScope->Type;
    //convert to script class
    //assert(ParentType->Properties&& ParentType->Functions && "Parent type is not script type");

    //load virtual function table first
    *InByteCodeFileReader>>ParentType->Functions->VirtualFunctionTable;


    while(InByteCodeFileReader->GetOffset() < InClassEndByte)
    {
        EByteCodeSectionType Instruction;
        *InByteCodeFileReader >> Instruction;

        switch (Instruction)
        {
        case EByteCodeSectionType::Field:
        {
            // uint32_t FieldContentLength;
            // *InByteCodeFileReader >> FieldContentLength;

            CByteCodeFile::SFieldInfo FieldInfo;
            *InByteCodeFileReader >> FieldInfo;

            // auto DefferedField = [
            //     ParentType 
            //     ,this
            //     ,FieldInfo
            //     ,InByteCodeFileReader
            //     ]()
            {
                SDecoratedType FieldType =InByteCodeFileReader->ParseTypeInfo(FieldInfo.Type);

                //CType * ParentType = ParentType;
                //convert to script class
                // assert(ParentType->IsScriptClassType() && "Parent type is not script class");
                // CGSharpClass* ParentClass = static_cast<CGSharpClass*>(ParentType);

                assert(ParentType->Properties->IsScriptPropertyList() && "Parent type is not script class");
                CGSharpPropertyList * Fields = 
                    reinterpret_cast<CGSharpPropertyList*>(ParentType->Properties);

                auto NewField= new CGSharpFieldProperty(
                    Fields
                    ,FieldInfo.Name
                    ,FieldType
                    ,TMap<EPropertyFlags,  SReflectionMetaDataValue>()
                    ,FieldInfo.Decorator.bIsStatic
                    ,FieldInfo.FieldIndex
                    ,ParentType->IsStructType()
                    ,ParentType->IsEnumType()
                    ,FieldInfo.DefaultValue
                );

                ParentType->Properties->AddProperty(NewField);

            };

            //InByteCodeFileReader->SetOffset(EndIndex+1);

            // if(DeferredFunctions.Contains(ParentType))
            // {
            //     DeferredFunctions[ParentType].Add(DefferedField);
            // }
            // else
            // {
            //     TVector<std::function<void()>> DefferedFields;
            //     DefferedFields.Add(DefferedField);
            //     DeferredFunctions.Add(ParentType,DefferedFields);
            // }

            break;

        }

        case EByteCodeSectionType::Function:
        {
            uint32_t FunctionContentLength;
            *InByteCodeFileReader >> FunctionContentLength;

            uint32_t EndIndex= InByteCodeFileReader->GetOffset() + FunctionContentLength-1;
            std::shared_ptr<CByteCodeFile> File = InByteCodeFileReader->GetByteCodeFile();

            CByteCodeFile::SFunctionInfo FunctionInfo;
            *InByteCodeFileReader >> FunctionInfo;
            auto FunctionContentStart= InByteCodeFileReader->GetOffset();

            // auto DefferedFunction = [
            //     EndIndex
            //     ,ParentType 
            //     ,File
            //     ,this
            //     ,FunctionInfo
            //     ,FunctionContentStart
            //     ,InByteCodeFileReader
            //     ]()
            {

                SDecoratedType ReturnType = InByteCodeFileReader->ParseTypeInfo(FunctionInfo.ReturnType);
                TVector<SScriptVariable> VariableStack;
                for (auto& VariableInfo : FunctionInfo.VariableStack)
                {
                    CType* Type= InByteCodeFileReader->ParseTypeInfo(VariableInfo.Type).BasicType;
                    assert(Type);
                    VariableStack.Emplace(VariableInfo.Name,Type);
                }

                //CType* ParentType = InScope->Type;
                //convert to script class
                // assert(ParentClassType->IsScriptClassType() && "Parent type is not script class");
                // CGSharpClass* ParentClass = static_cast<CGSharpClass*>(ParentClassType);

                TVector<SDecoratedType> Parameters;
                TVector<CString> ParameterNames;
                for (auto& ParameterInfo : FunctionInfo.Parameters)
                {
                    SDecoratedType ParameterType = InByteCodeFileReader->ParseTypeInfo(ParameterInfo.Type);
                    Parameters.Add(ParameterType);
                    ParameterNames.Add(ParameterInfo.Name);
                }

                CScriptFunction* NewFunction = new CScriptFunction(ParentType->Functions
                    ,FunctionInfo.Name
                    ,ReturnType
                    ,Parameters
                    ,ParameterNames
                    ,{}
                    );


                CFunctionTypeGeneralized* FunctionType= CFunctionType::FindFunctionType(ReturnType,Parameters);
                assert(FunctionType && "Function type not found");
                NewFunction->SetFunctionType(FunctionType);

                if(FunctionInfo.IsStatic())
                {
                    NewFunction->SetStatic();
                }
                
                //add parameters
                // for (auto& ParameterTypeInfo : FunctionInfo.ParameterTypes)
                // {
                //     CType* ParameterType = ByteCodeReader->ParseTypeInfo(ParameterTypeInfo);
                //     NewFunction->Parameters.Add(ParameterType);
                // }

                TVector<std::shared_ptr<CBreakpointSlot>> BreakpointSlots;
                BreakpointSlots.Resize(FunctionInfo.LinePositions.Num());

                for(auto Slot: FunctionInfo.BreakablePoints)
                {
                    auto File= AllScriptFiles[Slot.FileIndex];
                    auto BreakpointSlot= File->BreakpointSlots[Slot.SourceCodeLineNumber];
                    if(!BreakpointSlot)
                    {
                        BreakpointSlot= std::make_shared<CBreakpointSlot>();
                        BreakpointSlot->ScriptFile=File;
                        BreakpointSlot->LineIndex=Slot.SourceCodeLineNumber;
                        
                        File->BreakpointSlots[Slot.SourceCodeLineNumber]=BreakpointSlot;
                    }
                    BreakpointSlots[Slot.InstructionIndex]=BreakpointSlot;
                }

                NewFunction->SetBody(File
                    ,FunctionContentStart
                    ,EndIndex
                    ,FunctionInfo.LinePositions
                    ,VariableStack
                    ,BreakpointSlots
                    );

            };

            InByteCodeFileReader->SetOffset(EndIndex+1);

            // if(DeferredFunctions.Contains(ParentType))
            // {
            //     DeferredFunctions[ParentType].Add(DefferedFunction);
            // }
            // else
            // {
            //     TVector<std::function<void()>> DefferedFunctionsForType;
            //     DefferedFunctionsForType.Add(DefferedFunction);
            //     DeferredFunctions.Add(ParentType,DefferedFunctionsForType);
            // }

            //DeferredFunctions.Add(ParentType,DefferedFunction);

            break;

        }

        default:
            assert(false && "Unknown instruction");

        }
    }

    // auto CreateClassStaticFieldValues=[InScope](){

    //     assert(InScope->Type->IsScriptClassType() && "Only script classes can have static fields");
    //     //convert to script class
    //     CGSharpClass* ScriptClass = static_cast<CGSharpClass*>(InScope->Type);
    //     ScriptClass->OnPostConstruct();
    // };

    // DeferredFunctions.Add(CreateClassStaticFieldValues);
}
